#include <stdio.h>
#include <tool/yarray2D.h>

/** 2 dimensional array function data struct */
struct array2DData {
  /** A 2 dimensional array */
  void **a2DArray;
  /** 1 dimension length of the 2 dimensional array. */
  size_t len;
  /** current position within the 2 dimensional array. */
  int cur;
  /** This method is NOT provided, require customized function.
  This is NOT a compulsory function pointer. */
  g_reply (*compare)(const void *anObj1, const void *anObj2);
};

/*======= declare internal function */
boolean yarray2D_free(array2D *objP);
/* specific operation */
boolean yarray2D_free2DArray(void **array, size_t arrayLen);
boolean yarray2D_assign(array2D obj, 
    void **a2DArray, const size_t len,
    g_reply (*compare)(const void *anObj1, const void *anObj2));
boolean yarray2D_clear(array2D obj);
int yarray2D_find(const array2D obj, const void *anObject);
boolean yarray2D_setFnCompare(array2D obj, 
    g_reply (*compare)(const void *anObj1, const void *anObj2));

/*======= directly exposed function */
/** Create an empty array wrapper for the 2D array.
@return a newly created array wrapper, 
  or <code>NULL</code> if failure.
*/
array2D yarray2D_create(void) {
  array2D obj;
  FN_MALLOC_ERRMSG(obj, sizeof(struct array2D), 
      "Error: yarray2D_create(void) malloc(obj)")
  if (obj == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(obj->d, sizeof(struct array2DData), 
      "Error: yarray2D_create(void) malloc(obj->d)")
  if (obj->d == NULL) {yarray2D_free(&obj); return NULL;}
  /*--- init function pointers ---*/
  obj->free = yarray2D_free;
  /* specific operation */
  obj->free2DArray = yarray2D_free2DArray;
  obj->assign = yarray2D_assign;
  obj->clear = yarray2D_clear;
  obj->find = yarray2D_find;
  obj->setFnCompare = yarray2D_setFnCompare;
  return obj;
}

/** Create an array wrapper for the 2D array, 
with an 2 dimensional array inside.
<p>Provide a function to compare two objects of type <code>void *</code>.
The function is of the form 
<code>g_reply (*compare)(const void *, const void *)</code>.
</p>
<p>This function will be used for comparing a given object against 
another object currently pointing to within in the array.
<br/><code>anObject1</code> is the given object.
<br/><code>anObject2</code> is the current object within the array.
</p>
<p><code>compare</code> should return <code>g_replyEqual</code> 
if <code>anObject1</code> and <code>anObject2</code> are equal.
Returns <code>g_replyLesser</code> or <code>g_replyGreater</code> 
if <code>anObject1</code> is less than or greater than 
<code>anObject2</code> respectively.
</p>
<p>This is a customized function provided by the object's library 
and is NOT compulsory.
</p>
@param a2DArray the 2 dimensional array.
@param len the 1 dimension length of the 2 dimensional array.
@param compare the function to compare 
  two objects of type <code>void *</code>.
@return a newly created array wrapper, 
  or <code>NULL</code> if failure.
*/
array2D yarray2D_createWith(void **a2DArray, const size_t len,
    g_reply (*compare)(const void *anObj1, const void *anObj2)) {
  /* create an empty array2D */
  array2D obj = yarray2D_create();
  if (obj == NULL) {
    yarray2D_free(&obj);
    return NULL;
  }
  if (yarray2D_assign(obj, a2DArray, len, compare) == false) {
    yarray2D_free(&obj);
    return NULL;
  }
  return obj;
}

/*======= exposed through function pointers */
/** Free a <code>array2D</code>, free up the memory.
@param objP pass in the address of <code>array2D</code> to be free.
@return <code>true</code> if successful otherwise <code>false</code>.
*/
boolean yarray2D_free(array2D *objP) {
  /* NOT critical if objP is NULL, assume all's fine */
  if (*objP == NULL) {return true;}
  if ((*objP)->d != NULL) {
    /* free array2DData */
    free((*objP)->d);
    (*objP)->d = NULL;
  }
  /* free array2D */
  free(*objP);
  *objP = NULL;
  return true;
}

/** Free a 2 dimension array, free up the memory.
@param array pass in the 2D array to be free.
@param arrayLen the length of the 1st dimension of the <code>array</code>.
@return <code>true</code> if successful otherwise <code>false</code>.
*/
boolean yarray2D_free2DArray(void **array, size_t arrayLen) {
  if (arrayLen > 0 && array != NULL) {
    /* go through 1st dimension length to 
      free the 2nd dimension */
    do {
      arrayLen--;
      if (array[arrayLen] != NULL) {free(array[arrayLen]);}
    } while (arrayLen > 0);
    /* free 1st dimension */
    free(array);
  }
  return true;
}

/** Assign elements into this 2D array wrapper.
<p>Provide a function to compare two objects of type <code>void *</code>.
The function is of the form 
<code>g_reply (*compare)(const void *, const void *)</code>.
</p>
<p>This function will be used for comparing a given object against 
another object currently pointing to within in the array.
<br/><code>anObject1</code> is the given object.
<br/><code>anObject2</code> is the current object within the array.
</p>
<p><code>compare</code> should return <code>g_replyEqual</code> 
if <code>anObject1</code> and <code>anObject2</code> are equal.
Returns <code>g_replyLesser</code> or <code>g_replyGreater</code> 
if <code>anObject1</code> is less than or greater than 
<code>anObject2</code> respectively.
</p>
<p>This is a customized function provided by the object's library 
and is NOT compulsory.
</p>
@param obj the 2 dimensional array object.
@param a2DArray the 2 dimensional array.
@param len the 1 dimension length of the 2 dimensional array.
@param compare the function to compare 
  two objects of type <code>void *</code>.
@return a newly created array wrapper, 
  or <code>NULL</code> if failure.
*/
boolean yarray2D_assign(array2D obj, 
    void **a2DArray, const size_t len,
    g_reply (*compare)(const void *anObj1, const void *anObj2)) {
  if (obj == NULL || a2DArray == NULL || len <= 0 
      || compare == NULL) {
    return false;
  }
  /*--- data ---*/
  obj->d->a2DArray = a2DArray;
  obj->d->len = len;
  obj->d->cur = 0;
  obj->d->compare = compare;
  return true;
}

/** Removes reference to all assignable elements 
within <code>array2D</code>. 
Function does NOT free memory of the 2 dimension array.
@param obj the 2 dimensional array object.
@return <code>true</code> if successful otherwise <code>false</code>.
*/
boolean yarray2D_clear(array2D obj) {
  obj->d->a2DArray = NULL;
  obj->d->len = -1;
  obj->d->compare = NULL;
  return true;
}

/** find a position within the 2 dimension array that match this object.
Will use the function that 
compare two objects of type <code>void *</code>.
The function is of the form 
<code>g_reply (*compare)(const void *, const void *)</code>.
@param obj the 2 dimensional array object.
@param anObject find this object in the array.
@return the position within the 2 dimension array or -1 if failure.
*/
int yarray2D_find(const array2D obj, const void *anObject) {
  if (obj == NULL || obj->d == NULL || anObject == NULL
      || obj->d->a2DArray == NULL || obj->d->compare == NULL) {
    return -1;
  }
  /* compare current before searching from beginning. */
  if (obj->d->cur > 0) {
    if (obj->d->compare(anObject, obj->d->a2DArray[obj->d->cur]) 
        == g_replyEqual) {
      return obj->d->cur;
    }
  }
  /* search from beginning of the array */
  int i = 0;
  do {
    if (obj->d->compare(anObject, obj->d->a2DArray[i]) 
        == g_replyEqual) {
      obj->d->cur = i;
      return i;
    }
    i++;
  } while (i < obj->d->len);
  return -1;
}

/** Provide a function to compare two objects of type <code>void *</code>.
The function is of the form 
<code>g_reply (*compare)(const void *, const void *)</code>.
<p>This function will be used for comparing a given object against 
another object currently pointing to within in the array.
<br/><code>anObject1</code> is the given object.
<br/><code>anObject2</code> is the current object within the array.
</p>
<p><code>compare</code> should return <code>g_replyEqual</code> 
if <code>anObject1</code> and <code>anObject2</code> are equal.
Returns <code>g_replyLesser</code> or <code>g_replyGreater</code> 
if <code>anObject1</code> is less than or greater than 
<code>anObject2</code> respectively.
</p>
<p>This is a customized function provided by the object's library 
and is NOT compulsory.
</p>
@param obj the 2 dimensional array object.
@param compare the function to compare 
  two objects of type <code>void *</code>.
@return <code>true</code> if successful otherwise <code>false</code>.
*/
boolean yarray2D_setFnCompare(array2D obj, 
    g_reply (*compare)(const void *anObj1, const void *anObj2)) {
  if (obj == NULL || obj->d == NULL || compare == NULL) {
    return false;
  }
  obj->d->compare = compare;
  return true;
}
